/* Viper Split# Compiler
 * Copyright (c) 2013 Split# Developers
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 */


/* NOTE: The above license DOES NOT APPLY to the Viper and GruntXProductions
 * namespace. Viper is released under the GNU General Public License v3
 */

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using GruntXProductions;

namespace SplitSharp
{
        public class Parser
        {
                public List<Token> tokens = new List<Token> ();
                private int index = 0;
                public List<CodeElement> AST = new List<CodeElement> ();

                public Parser (List<Token> tok)
                {
                        tokens = tok;
                }

                private Token readToken ()
                {
                        ++index;
                        return tokens [index - 1];
                }

                private Token peekToken (int depth = 0)
                {
                        if (depth + index >= tokens.Count)
                                return new Tokens.Statement ();
                        return tokens [depth + index];
                }

                private Expression ParseExpression ()
                {
                        Expression exp = new Expression ();
                        if (!(readToken () is Tokens.openParenthesis))
                                throw new Exception ("Expected (!");
                        while (!(peekToken() is Tokens.closeParenthesis) && !(peekToken() is Tokens.closeBracket)) {
                                exp.tokens.Add (ParseItem ());
                        }
                        readToken ();
                        return exp;
                }

                public CodeBlock ParseBlock ()
                {
                        CodeBlock cb = new CodeBlock ();
                        if (!(readToken () is Tokens.openCurlyBracket))
                                throw new Exception ("Expected {");
                        while (!(peekToken() is Tokens.closeCurlyBracket)) {
                                cb.Body.Add (ParseLine ());
                        }
                        readToken ();
                        return cb;
                }

                public Function ParseFunction ()
                {
                        readToken ();
                        if (!(readToken () is Tokens.Colon))
                                throw new Exception ("Expected :");
                        string type = readToken ().ToString ();
                        string name = readToken ().ToString ();

                        Function func = new Function ();
                        func.Name = name;
                        func.Type = type;
                        if (!(readToken () is Tokens.openParenthesis))
                                throw new Exception ("Expected ( ");
                        while (!(peekToken() is Tokens.closeParenthesis)) {
                                func.Arguments.Add ((Declaration)declareVar (false));
                                if (peekToken () is Tokens.closeParenthesis) {
                                } else if (!(peekToken () is Tokens.Comma)) {

                                        AST.Add (new Error ("Expected ) or , at line " + peekToken ().lineNumber.ToString ()));
                                } else
                                        readToken ();
                        }
                        readToken ();
                        func.body = ParseBlock ();
                        return func;
                }

                public void BeginParse ()
                {
                        while (index < tokens.Count) {
                                if (peekToken ().ToString () == "function") {
                                        AST.Add (ParseFunction ());
                                } else if (peekToken ().ToString () == "class")
                                        AST.Add (ParseClass ());
                                else if (peekToken ().ToString () == "external") {
                                        readToken ();
                                        if (!(readToken () is Tokens.Colon))
                                                throw new Exception ("Expected :");
                                        string type = readToken ().ToString ();
                                        string name = readToken ().ToString ();

                                        Function func = new Function ();
                                        func.Name = name;
                                        func.External = true;
                                        func.Type = type;
                                        if (!(readToken () is Tokens.openParenthesis))
                                                throw new Exception ("Expected ( ");
                                        while (!(peekToken() is Tokens.closeParenthesis)) {
                                                func.Arguments.Add ((Declaration)declareVar (false));
                                                if (peekToken () is Tokens.closeParenthesis) {
                                                } else if (!(peekToken () is Tokens.Comma)) {

                                                        AST.Add (new Error ("Expected ) or , at line " + peekToken ().lineNumber.ToString ()));
                                                } else
                                                        readToken ();
                                        }
                                        readToken ();
                                        if (!(readToken () is Tokens.SemiColon))
                                                throw new Exception ("; expected");
                                        func.body = new CodeBlock ();
                                        AST.Add (func);
                                } else if (peekToken () is Tokens.At) {
                                        readToken ();
                                        if (peekToken ().ToString () == "import") {
                                                readToken ();
                                                string lib = readToken ().ToString ();
                                                Lexer l = new Lexer ();
                                                l.Scan (new StringReader (File.ReadAllText (System.Windows.Forms.Application.StartupPath + "/lib/" + lib)));
                                                Parser p = new Parser (l.tokens);
                                                p.BeginParse ();
                                                p.AST.AddRange (this.AST);
                                                this.AST = p.AST;

                                        }
                                }
                        }
                }

                public Instance declareObject ()
                {
                        Instance ret = new Instance ();
                        readToken ();
                        if (!(readToken () is Tokens.Colon))
                                throw new Exception ("Expected ;");
                        string name = readToken ().ToString ();
                        ret.Name = name;
                        if (!(readToken () is Tokens.Assign))
                                throw new Exception ("Expected =");
                        if (readToken ().ToString () != "new")
                                throw new Exception ("New expected");
                        ret.Ctor = ParseCall ();
                        if (!(readToken () is Tokens.SemiColon))
                                throw new Exception ("Expected ;");
                        return ret;
                }

                public CodeElement declareVar (bool checksemi = true)
                {
                        if (peekToken ().ToString () == "var") {
                                readToken ();
                                if (!(readToken () is Tokens.Colon))
                                        throw new Exception ("Expected :");
                                string type = readToken ().ToString ();
                                Declaration decl = new Declaration ();
                                decl.Name = readToken ().ToString ();
                                decl.Type = type;
                                if (peekToken () is Tokens.Assign) {
                                        readToken ();
                                        Assign asn = new Assign ();
                                        asn.Variable = decl.Name;
                                        while (!(peekToken() is Tokens.SemiColon)) {
                                                asn.Value.Add (ParseItem ());
                                        }
                                        decl.Value = asn;
                                        readToken ();
                                } else if (peekToken () is Tokens.openBracket) {
                                        readToken ();
                                        if (!(peekToken () is Tokens.IntLiteral)) {
                                                readToken ();
                                                return new Error ("Expected fixed size for fixed array " + decl.Name);
                                        } else {
                                                Tokens.IntLiteral il = readToken () as Tokens.IntLiteral;
                                                FixedArray array = new FixedArray ();
                                                array.Size = il.Value;
                                                array.Type = decl.Type;
                                                array.Name = decl.Name;
                                                if (!(readToken () is Tokens.closeBracket))
                                                        return new Error ("Expected [");
                                                if (!(peekToken () is Tokens.SemiColon))
                                                        return new Error ("Expected ; at " + peekToken ().lineNumber);
                                                readToken ();
                                                return array;

                                        }

                                } else if (checksemi) {
                                        if (!(peekToken () is Tokens.SemiColon))
                                                return new Error ("Expected ; at " + peekToken ().lineNumber);
                                        readToken ();
                                }
                                return decl;
                        }
                        throw new Exception ("Variable declaration expected!");
                }

                public BooleanComparison ParseBooleanExpression ()
                {
                        Expression leftHand = new Expression ();
                        Expression rightHand = new Expression ();
                        bool doLeft = true;
                        BooleanComparison ret = new BooleanComparison ();
                        while (true) {
                                Token token = peekToken ();
                                if (token is Tokens.Equal) {
                                        readToken ();
                                        doLeft = false;
                                        ret.Operation = BoolOp.Equal;
                                } else if (token is Tokens.NotEqual) {
                                        readToken ();
                                        doLeft = false;
                                        ret.Operation = BoolOp.NotEqual;
                                } else if (token is Tokens.GreaterThan) {
                                        readToken ();
                                        doLeft = false;
                                        ret.Operation = BoolOp.GreaterThan;
                                } else if (token is Tokens.LessThan) {
                                        readToken ();
                                        doLeft = false;
                                        ret.Operation = BoolOp.LessThan;
                                } else if (token is Tokens.closeParenthesis) {
                                        readToken ();
                                        break;
                                } else if (token is Tokens.SemiColon) {
                                        break;
                                } else if (token is Tokens.openParenthesis) {
                                        readToken ();
                                        if (doLeft)
                                                leftHand.tokens.Add (ParseExpression ());
                                        else
                                                rightHand.tokens.Add (ParseExpression ());
                                } else {
                                        if (doLeft)
                                                leftHand.tokens.Add (ParseItem ());
                                        else
                                                rightHand.tokens.Add (ParseItem ());
                                }

                        }
                        ret.Expression1 = leftHand;
                        ret.Expression2 = rightHand;
                        return ret;

                }

                public CodeObject ParseClass ()
                {
                        CodeObject co = new CodeObject ();
                        readToken ();
                        string name = readToken ().ToString ();
                        co.Name = name;
                        if (!(readToken () is Tokens.openCurlyBracket))
                                throw new Exception ("{ expected");
                        while (!(peekToken() is Tokens.closeCurlyBracket)) {
                                if (peekToken ().ToString () == "function")
                                        co.methods.Add (ParseFunction ());
                        }
                        readToken ();
                        return co;

                }

                public FunctionCall ParseCall ()
                {
                        string name = readToken ().ToString ();
                        FunctionCall ret = new FunctionCall ();
                        ret.Name = name;
                        Expression exp = new Expression ();
                        if (readToken () is Tokens.openParenthesis) {
                                if (peekToken () is Tokens.closeParenthesis) {
                                        readToken ();
                                        return ret;
                                }
                                while (true) {
                                        while (!(peekToken() is Tokens.Comma)) {
                                                if (peekToken () is Tokens.closeParenthesis) {
                                                        readToken ();

                                                        ret.Arguments.Add (exp);

                                                        return ret;
                                                } else
                                                        exp.tokens.Add (ParseItem ());
                                        }
                                        readToken ();
                                        ret.Arguments.Add (exp);
                                        exp = new Expression ();
                                }
                        } else
                                throw new Exception ("Expected ( after " + name);
                }

                public Token ParseItem ()
                {
                        if (peekToken () is Tokens.Statement && peekToken (1) is Tokens.openParenthesis) {
                                return ParseCall ();
                        } else if (peekToken () is Tokens.And && peekToken (1) is Tokens.Statement) {
                                AddressOf ao = new AddressOf ();
                                readToken ();
                                ao.Symbol = readToken ().ToString ();
                                return ao;
                         } else if (peekToken () is Tokens.At ) {
                                PointerDereference pd = new PointerDereference();
                                readToken ();
                                pd.Pointer = ParseItem();
                                return pd;
                        } else if (peekToken () is Tokens.Statement && peekToken (1) is Tokens.openBracket) {
                                string name = readToken ().ToString ();
                                readToken ();
                                ArrayRetrieve ar = new ArrayRetrieve ();
                                ar.Variable = name;
                                while (!(peekToken() is Tokens.closeBracket)) {
                                        ar.IndexExpression.Add (ParseItem ());
                                }
                                readToken ();
                                return ar;
                        } else if (peekToken () is Tokens.openParenthesis) {
                                return ParseExpression ();
                        } else
                                return readToken ();

                }

                public CodeElement ParseLine ()
                {
                        if (peekToken ().ToString () == "if") {
                                readToken ();
                                IfStatement ifstat = new IfStatement ();
                                readToken ();
                                ifstat.Expression = ParseBooleanExpression ();
                                ifstat.ifBody = ParseBlock ();
                                if (peekToken ().ToString () == "else") {
                                        if (peekToken (1).ToString () == "if") {
                                                readToken ();
                                                ifstat.elseBody.Body.Add (ParseLine ());

                                        } else {
                                                readToken ();
                                                ifstat.elseBody = ParseBlock ();
                                        }
                                }
                                return ifstat;
                        } else if (peekToken ().ToString () == "try") {
                                readToken ();
                                TryBody Try = new TryBody ();
                                Try.body = ParseBlock ();
                                if (readToken ().ToString () != "catch")
                                        throw new Exception ("Expected catch");

                                CatchBody bd = new CatchBody ();

                                bd.body = ParseBlock ();
                                Try.Catch = bd;

                                return Try;
                        } else if (peekToken ().ToString () == "for") {
                                readToken ();
                                ForStatement forstat = new ForStatement ();
                                readToken ();
                                forstat.Declaration = ParseLine ();
                                forstat.Comparison = ParseBooleanExpression ();
                                if (!(readToken () is Tokens.SemiColon))
                                        throw new Exception ("Expected ; at line at " + peekToken ().lineNumber.ToString ());
                                forstat.Operation = ParseLine ();
                                if (!(readToken () is Tokens.closeParenthesis))
                                        throw new Exception ("Expected ) at line " + peekToken ().lineNumber.ToString ());
                                forstat.forBody = ParseBlock ();
                                return forstat;
                        } else if (peekToken ().ToString () == "while") {
                                readToken ();
                                WhileStatement whilestat = new WhileStatement ();
                                readToken ();
                                whilestat.Comparison = ParseBooleanExpression ();
                                whilestat.whileBody = ParseBlock ();
                                return whilestat;
                        } else if (peekToken ().ToString () == "asm") {
                                readToken ();
                                if (!(readToken () is Tokens.openParenthesis))
                                        throw new Exception ("Expected (");
                                if (peekToken () is Tokens.StringLiteral) {
                                        Tokens.StringLiteral sl = readToken () as Tokens.StringLiteral;
                                        inlineAsm asm = new inlineAsm ();
                                        asm.Asm = sl.Value;
                                        if (!(readToken () is Tokens.closeParenthesis))
                                                throw new Exception ("Expected )");
                                        if (!(peekToken () is Tokens.SemiColon))
                                                return new Error ("Expected ; at " + peekToken ().lineNumber);
                                        readToken ();
                                        return asm;
                                } else
                                        throw new Exception ("Expected string literal!");

                        } else if (peekToken ().ToString () == "var") {
                                return declareVar ();
                        } else if (peekToken ().ToString () == "object") {
                                return declareObject ();
                        } else if (peekToken ().ToString () == "meta") {
                                readToken ();
                                if (!(readToken () is Tokens.Colon))
                                        throw new Exception ("Expected :");
                                string str = readToken ().ToString ();
                                MetaElement me = new MetaElement ();
                                me.Tag = str;
                                if (!(readToken () is Tokens.Assign))
                                        throw new Exception ("Expected =");
                                Tokens.StringLiteral sl = readToken () as Tokens.StringLiteral;
                                me.Value = sl.Value;
                                if (!(readToken () is Tokens.SemiColon))
                                        throw new Exception ("Expected ;");
                                return me;
                        } else if (peekToken ().ToString () == "return") {
                                readToken ();
                                Return ret = new Return ();
                                if (peekToken () is Tokens.SemiColon) {
                                        readToken ();
                                } else {
                                        while (!(peekToken() is Tokens.SemiColon)) {
                                                ret.Value.Add (ParseItem ());
                                        }
                                        readToken ();
                                }
                                return ret;
                        } else if (peekToken () is Tokens.Statement && peekToken (1) is Tokens.ObjectReference) {
                                ObjectAssign oa = new ObjectAssign ();
                                oa.Ref = readToken ().ToString ();
                                readToken ();
                                oa.Variable = readToken ().ToString ();
                                while (!(peekToken() is Tokens.SemiColon)) {
                                        oa.Value.Add (ParseItem ());
                                }
                                readToken ();
                                return oa;
                        } else if (peekToken () is Tokens.Statement && peekToken (1) is Tokens.Assign) {
                                string name = readToken ().ToString ();
                                readToken ();
                                Assign asn = new Assign ();
                                asn.Variable = name;
                                while (!(peekToken() is Tokens.SemiColon)) {
                                        asn.Value.Add (ParseItem ());
                                }
                                readToken ();
                                return asn;
                        } else if (peekToken () is Tokens.Statement && peekToken (1) is Tokens.openBracket) {
                                ArrayAssign asn = new ArrayAssign ();
                                string name = readToken ().ToString ();
                                asn.Variable = name;
                                readToken ();
                                while (!(peekToken() is Tokens.closeBracket))
                                        asn.Index.Add (ParseItem ());
                                readToken ();
                                if (readToken () is Tokens.Assign) {
                                        while (!(peekToken() is Tokens.SemiColon)) {
                                                asn.Value.Add (ParseItem ());
                                        }
                                        readToken ();
                                        return asn;
                                } else
                                        throw new Exception ("Expected =");
                        } else if (peekToken () is Tokens.Statement && (peekToken (1) is Tokens.Increment || peekToken (1) is Tokens.Decrement)) {

                                Assign asn = new Assign ();
                                asn.Variable = peekToken ().ToString ();
                                asn.Value.Add (readToken ());
                                Token op = readToken ();
                                if (op is Tokens.Increment)
                                        asn.Value.Add (new Tokens.Add ());
                                else if (op is Tokens.Decrement)
                                        asn.Value.Add (new Tokens.Sub ());
                                Tokens.IntLiteral il = new Tokens.IntLiteral ();
                                il.Value = 1;
                                asn.Value.Add (il);
                                if (!(peekToken () is Tokens.SemiColon))
                                        return new Error ("Expected ; at " + peekToken ().lineNumber);
                                readToken ();
                                return asn;

                        } else if (peekToken () is Tokens.Statement && peekToken (1) is Tokens.PlusEquals) {

                                Assign asn = new Assign ();
                                asn.Variable = peekToken ().ToString ();
                                asn.Value.Add (readToken ());
                                Token op = readToken ();
                                if (op is Tokens.PlusEquals)
                                        asn.Value.Add (new Tokens.Add ());
                                else if (op is Tokens.Decrement)
                                        asn.Value.Add (new Tokens.Sub ());
                                while (!(peekToken() is Tokens.SemiColon)) {
                                        asn.Value.Add (ParseItem ());
                                }
                                readToken ();
                                return asn;

                        } else if (peekToken () is Tokens.Statement && peekToken (1) is Tokens.openParenthesis) {
                                FunctionCall fc = ParseCall ();
                                if (!(peekToken () is Tokens.SemiColon))
                                        return new Error ("Expected ; at " + peekToken ().lineNumber);
                                readToken ();
                                return fc;
                        } else {
                                //return new CodeElement();
                                throw new Exception ("Unexpected " + peekToken ().ToString () + " at line " + peekToken ().lineNumber.ToString ());
                        }
                }
        }
}
